package apii.algorithm.tokenizer;

import java.util.*;

/**
 * <b> Note:</b> NOT thread safe
 * <p>
 * <b> Note:</b> Doesn't create threads.
 * 
 * @author Alex
 * 
 */

public class JavaStringTokenizer {

	public static final int EOF = -1;
	public static final int IDENTIFIER = -2;
	public static final int KEYWORD = -3;
	public static final int SIGN = -4;
	public static final int LITERAL = -5;

	protected int type;
	protected int start;
	protected int end;
	protected String code;
	protected int pos;

	public JavaStringTokenizer(String s) {
		this.code = s;
	}

	public int nextToken() throws MalformedCodeException {
		while (readIgnored())
			;
		start = pos;
		if (!(readEOF() || readLiteral() || readIdentifier() || readSign()))
			throw new MalformedCodeException();
		end = pos;
		return type;
	}

	public int getType() {
		return type;
	}

	public int getStart() {
		return start;
	}

	public int getEnd() {
		return end;
	}

	public String getString() {
		return code.substring(start, end);
	}

	private boolean readEOF() {
		type = EOF;
		return pos == code.length();
	}

	private boolean readLiteral() {
		type = LITERAL;
		return readHexFloatingPointLiteral() || readHexIntegerLiteral()
				|| readDecimalFloatingPointLiteral()
				|| readDecimalIntegerLiteral() || readCharacterLiteral('"')
				|| readCharacterLiteral('\'');
	}

	private boolean readCharacterLiteral(char c) {
		if (!(pos < code.length() && code.charAt(pos) == c))
			return false;
		int pos1 = pos + 1;
		for (;; ++pos1) {
			if (pos1 >= code.length())
				return false;
			if (code.charAt(pos1) == c)
				break;
			if (code.charAt(pos1) == '\\')
				++pos1;
		}
		pos = pos1 + 1;
		return true;
	}

	private boolean readHexFloatingPointLiteral() {
		if (!code.startsWith("0x", pos))
			return false;
		int pos1 = pos + 2;
		boolean was1 = false;
		boolean was2 = false;
		while (pos1 < code.length() && isHex(code.charAt(pos1))) {
			was2 = true;
			++pos1;
		}
		if (pos1 < code.length() && code.charAt(pos1) == '.') {
			was2 = true;
			was1 = true;
			++pos1;
			boolean was = false;
			while (pos1 < code.length() && isHex(code.charAt(pos1))) {
				++pos1;
				was = true;
			}
			if (!was)
				return false;
		}
		if (pos1 < code.length()
				&& Character.toUpperCase(code.charAt(pos1)) == 'P') {
			was1 = true;
			++pos1;
			if (pos1 < code.length()
					&& (code.charAt(pos1) == '+' || code.charAt(pos1) == '-'))
				++pos1;
			boolean was = false;
			while (pos1 < code.length() && Character.isDigit(code.charAt(pos1))) {
				++pos1;
				was = true;
			}
			if (!was)
				return false;
		}
		if (pos1 < code.length()
				&& (Character.toUpperCase(code.charAt(pos1)) == 'F' || Character
						.toUpperCase(code.charAt(pos1)) == 'D')) {
			was1 = true;
			++pos1;
		}
		if (!(was1 && was2))
			return false;
		pos = pos1;
		return true;
	}

	private boolean readDecimalFloatingPointLiteral() {
		int pos1 = pos;
		boolean was1 = false;
		boolean was2 = false;
		while (pos1 < code.length() && Character.isDigit(code.charAt(pos1))) {
			was2 = true;
			++pos1;
		}
		if (pos1 < code.length() && code.charAt(pos1) == '.') {
			was2 = true;
			was1 = true;
			++pos1;
			boolean was = false;
			while (pos1 < code.length() && Character.isDigit(code.charAt(pos1))) {
				++pos1;
				was = true;
			}
			if (!was)
				return false;
		}
		if (pos1 < code.length()
				&& Character.toUpperCase(code.charAt(pos1)) == 'E') {
			was1 = true;
			++pos1;
			if (pos1 < code.length()
					&& (code.charAt(pos1) == '+' || code.charAt(pos1) == '-'))
				++pos1;
			boolean was = false;
			while (pos1 < code.length() && Character.isDigit(code.charAt(pos1))) {
				++pos1;
				was = true;
			}
			if (!was)
				return false;
		}
		if (pos1 < code.length()
				&& (Character.toUpperCase(code.charAt(pos1)) == 'F' || Character
						.toUpperCase(code.charAt(pos1)) == 'D')) {
			was1 = true;
			++pos1;
		}
		if (!(was1 && was2))
			return false;
		pos = pos1;
		return true;
	}

	private boolean readHexIntegerLiteral() {
		if (!code.startsWith("0x", pos))
			return false;
		pos += 2;
		while (pos < code.length() && isHex(code.charAt(pos)))
			++pos;
		if (pos < code.length()
				&& Character.toUpperCase(code.charAt(pos)) == 'L')
			++pos;
		return true;
	}

	private static boolean isHex(char c) {
		return Character.isDigit(c) || 'a' <= c && c <= 'f' || 'A' <= c
				&& c <= 'F';
	}

	private boolean readDecimalIntegerLiteral() {
		boolean was = false;
		while (pos < code.length() && Character.isDigit(code.charAt(pos))) {
			was = true;
			++pos;
		}
		if (pos < code.length()
				&& Character.toUpperCase(code.charAt(pos)) == 'L')
			++pos;
		return was;
	}

	private static final Set<String> LITERALS = new HashSet<String>(
			Arrays.asList(new String[] { "true", "false", "null" }));
	private static final Set<String> KEYWORD_SET = new HashSet<String>(
			Arrays.asList(new String[] { "abstract", "continue", "for", "new",
					"switch", "assert", "default", "if", "package",
					"synchronized", "boolean", "do", "goto", "private", "this",
					"break", "double", "implements", "protected", "throw",
					"byte", "else", "import", "public", "throws", "case",
					"enum", "instanceof", "return", "transient", "catch",
					"extends", "int", "short", "try", "char", "final",
					"interface", "static", "void", "class", "finally", "long",
					"strictfp", "volatile", "const", "float", "native",
					"super", "while" }));
	private static List<String> SIGNS = Arrays.asList(">>>=", "...", "<<=",
			">>=", ">>>", "==", "<=", ">=", "!=", "&&", "||", "++", "--", "<<",
			">>", "+=", "-=", "*=", "/=", "&=", "|=", "^=", "%=", "=", ">",
			"<", "!", "~", "?", ":", "+", "-", "*", "/", "&", "|", "^", "%",
			"(", ")", "{", "}", "[", "]", ";", ",", ".", "@");

	private boolean readIdentifier() {
		type = IDENTIFIER;
		if (!(pos < code.length() && Character.isJavaIdentifierStart(code
				.charAt(pos))))
			return false;
		int pos1 = pos;
		++pos;
		while (pos < code.length()
				&& Character.isJavaIdentifierPart(code.charAt(pos)))
			++pos;
		String id = code.substring(pos1, pos);
		if (LITERALS.contains(id))
			type = LITERAL;
		if (KEYWORD_SET.contains(id))
			type = KEYWORD;
		return true;
	}

	private boolean readSign() {
		type = SIGN;
		for (String s : SIGNS) {
			if (code.startsWith(s, pos)) {
				pos += s.length();
				return true;
			}
		}
		return false;
	}

	private boolean readIgnored() {
		return readSpace() || readComment();
	}

	private boolean readSpace() {
		if (!(pos < code.length() && Character.isWhitespace(code.charAt(pos))))
			return false;
		++pos;
		return true;
	}

	private boolean readComment() {
		return readGeneralComment("/*", "*/") || readGeneralComment("//", "\n");
	}

	private boolean readGeneralComment(String start, String finish) {
		if (!code.startsWith(start, pos))
			return false;
		int pos1 = code.indexOf(finish, pos + start.length());
		if (pos1 == -1)
			return false;
		pos = pos1 + finish.length();
		return true;
	}

	// public static void main(String[] args) {
	// new Thread(new Runnable() {
	//
	// @Override
	// public void run() {
	// try {
	// BufferedReader r = new BufferedReader(
	// new FileReader(
	// "D:\\workspace\\API\\test.java"));
	// StringBuilder sb = new StringBuilder();
	// try {
	// String line;
	// while ((line = r.readLine()) != null)
	// sb.append(line + "\n");
	// } finally {
	// r.close();
	// }
	// JavaStringTokenizer st = new JavaStringTokenizer(sb + "");
	// while (st.nextToken() != JavaStringTokenizer.EOF) {
	// System.out.print(st.getString() + "\t\t");
	// switch (st.getType()) {
	// case JavaStringTokenizer.IDENTIFIER:
	// System.out.println("Identifier");
	// break;
	// case JavaStringTokenizer.KEYWORD:
	// System.out.println("Keyword");
	// break;
	// case JavaStringTokenizer.LITERAL:
	// System.out.println("Literal");
	// break;
	// case JavaStringTokenizer.SIGN:
	// System.out.println("SIGN");
	// break;
	// default:
	// System.out.println("Other");
	// break;
	// }
	// }
	// r.close();
	// } catch (Exception e) {
	// e.printStackTrace();
	// }
	// }
	// }).start();
	// }
}
